package com.anji.hyperneat.modular;

import java.util.ArrayList;

import com.anji.integration.Activator;
import com.anji.nn.AnjiNet;
import com.anji.util.ActivationFacade;
import com.anji.util.Properties;

/**
 * Class to handle input / output mapping for a CPPN; input and output 
 * plugins may be registered, and their values will automatically be retrieved/set when needed.
 * @author slusk
 *
 */
public class CppnMapper {

	protected ArrayList<ICppnInputMapper> inputPlugins;
	protected ArrayList<ICppnOutputMapper> outputPlugins;
	
	protected Activator cppn;
	
	protected float[] inputs;
	protected float[] outputs;
	
	protected int numCppnInputs;
	
	/**
	 * Constructor.
	 * @param cppn The compositional pattern producing network that will be used for activation.
	 */
	public CppnMapper(Activator cppn) {
		init(null);
		this.cppn = cppn;
	}
	
	/**
	 * Constructor with configuration initialization.
	 * @param cppn The compositional pattern producing network that will be used for activation.
	 * @param props The properties for use in configuring this Mapper.
	 */
	public CppnMapper(Activator cppn, Properties props) {
		init(props);
		this.cppn = cppn;

	}

	/**
	 * 
	 * @param props
	 */
	public void init(Properties props) {
		inputPlugins = new ArrayList<ICppnInputMapper>();
		outputPlugins = new ArrayList<ICppnOutputMapper>();
	}
	
	/**
	 * Tally up the input count, based on how many inputs each input plugin has.
	 * @return
	 */
	protected int countInputs() {
		int count = 0;
		for (ICppnInputMapper inputPlugin : inputPlugins) {
			count += inputPlugin.getNumInputs();
		}
		return count;
	}
	
	/**
	 * Tally up the output count, based on how many outputs each input plugin has;
	 * for now, it is just one output per plugin.
	 * @return
	 */
	protected int countOutputs() {
		// for now, don't map multiple outputs per plugin
		return outputPlugins.size();
	}
	
	/**
	 * Add an input plugin to this mapper.
	 * @param plugin The input plugin
	 */
	public void registerInputPlugin(ICppnInputMapper plugin) {
		inputPlugins.add(plugin);
	}
	
	/**
	 * Add an output plugin to this mapper.
	 * @param plugin The output plugin
	 */
	public void registerOutputPlugin(ICppnOutputMapper plugin) {
		outputPlugins.add(plugin);
	}
	
	/**
	 * Set the Cppn for this mapper.
	 * @param cppn The Cppn
	 */
	public void setCppn(Activator cppn) {
		this.cppn = cppn;
	}
	
	/**
	 * Take the input values from each plugin in order, and plug them into an array for Cppn input;
	 * Note that plugins should have their inputs populated prior to calling this method.
	 */
	public void prepareInputs() {
		inputs = new float[countInputs()];
		int idx = 0;
		for (ICppnInputMapper inputMapper : inputPlugins) {
			float[] vals = inputMapper.getNormalizedInputs();
			for (float val : vals) {
				inputs[idx++] = val;
			}
		}
	}
	

	/**
	 * Activates the Cppn; prepare inputs should be called before this, and apply outputs should be called afterward.
	 */
	public void activate() {
		cppn.reset();
		outputs = cppn.next(inputs);
	}
	
	/**
	 * Takes the outputs of the network and sets them on each output plugin in order; should be called after activate()
	 */
	public void applyOutputs() {
		int idx = 0;
		for (ICppnOutputMapper outputPlugin : outputPlugins) {
			outputPlugin.accept( outputs[idx++] );
		}
	}
	
	
	/**
	 * FOR TESTING ONLY
	 * @param args
	 */
	public static void main(String[] args) {
		Activator cppn = new ActivationFacade(
				new int[] {7}
				, new int[] {3}
				, -3
				, 3);
		
		CppnMapper mapper = new CppnMapper(cppn);
		
		int[][] layerDimensions = new int[][] {
				{3,3}
				,{2,2}
		};
		
		// Register plugins
		BiasInputMapper biasInputMapper = new BiasInputMapper();
		SubNetCoordinateInputMapper subNetCoordsInputMapper = new SubNetCoordinateInputMapper(new int[] {2, 2});
		CoordinateInputMapper srcCoordInputMapper = new CoordinateInputMapper(layerDimensions, 2);
		CoordinateInputMapper tgtCoordInputMapper = new CoordinateInputMapper(layerDimensions, 2);
		
		mapper.registerInputPlugin(biasInputMapper);
		mapper.registerInputPlugin(subNetCoordsInputMapper);
		mapper.registerInputPlugin(srcCoordInputMapper);
		mapper.registerInputPlugin(tgtCoordInputMapper);
		
		OutputMapper weightMapper = new OutputMapper();
		OutputMapper biasMapper = new OutputMapper();
		OutputMapper lrMapper = new OutputMapper();
		
		mapper.registerOutputPlugin(weightMapper);
		mapper.registerOutputPlugin(biasMapper);
		mapper.registerOutputPlugin(lrMapper);
		
		// populate inputs
		subNetCoordsInputMapper.populateValues(new int[] {0,0});
		srcCoordInputMapper.populateValues(0, new int[] {0,2});
		tgtCoordInputMapper.populateValues(1, new int[] {1,0});
		
		// apply activate apply
		mapper.prepareInputs();
		mapper.activate();
		mapper.applyOutputs();

		// check vals
		System.out.println("Weight: " + weightMapper.getValue() );
		System.out.println("bias: " + biasMapper.getValue() );
		System.out.println("lr: " + lrMapper.getValue() );
	}

}
